package com.yenlien.traceyun.business.trace;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPObject;
import com.google.gson.JsonObject;
import com.mysql.cj.util.LogUtils;
import com.yenlien.traceyun.business.dao.CodeInfoDao;
import com.yenlien.traceyun.business.entity.CodeInfo;
import com.yenlien.traceyun.business.entity.TraceEntity;
import com.yenlien.traceyun.business.entity.TraceScanEntity;
import com.yenlien.traceyun.common.utils.JsonUtil;
import com.yenlien.traceyun.common.utils.LoggerUtil;
import com.yenlien.traceyun.webase.config.ApplicationContextConfig;
import com.yenlien.traceyun.webase.dao.BaseJpaDao;
import com.yenlien.traceyun.webase.dao.SqlQuery;
import com.yenlien.traceyun.webase.exception.BusinessException;
import org.bouncycastle.util.test.SimpleTest;
import org.hibernate.type.ObjectType;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import sun.reflect.generics.scope.ClassScope;

import java.io.FileDescriptor;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @author: bigwangy@sina.com
 * @create: 2020-07-23
 * @description:
 **/
@Service
public class TraceService {


    @Autowired
    private CodeInfoDao codeInfoDao;

    private Set<BeanDefinition> beanDefinitionSet;

    private Map<String, List<EntityTraceBean>> branchMap;

    private List<EntityTraceBean> traces;

    public TraceService() throws Exception {
        // 不使用默认的TypeFilter
        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
        provider.addIncludeFilter(new AnnotationTypeFilter(Trace.class));
        this.beanDefinitionSet = provider.findCandidateComponents("com.yenlien.traceyun");
        init();
    }


    /**
     * 对追溯流程的初始化
     *
     * @throws Exception
     */
    public void init() throws Exception {
        traces = new ArrayList<>();
        for (BeanDefinition beanDefinition : beanDefinitionSet) {
            EntityTraceBean bean = new EntityTraceBean(beanDefinition.getBeanClassName());
            traces.add(bean);
        }
        if (traces.size() <= 0) {
            throw new BusinessException("500", "未匹配到有关流程");
        }
        Collections.sort(traces);
        Map<String, List<EntityTraceBean>> map = new HashMap<>();
        branchMap = new HashMap<>();
        parseBranch(traces, map);
        for (Map.Entry<String, List<EntityTraceBean>> entry : map.entrySet()) {
            List<EntityTraceBean> root = getRoot(entry.getValue());
            tree(entry.getValue(), root);
            branchMap.put(entry.getKey(), root);
        }

    }

    /**
     * 扫码信息
     *
     * @param endStep
     * @throws Exception
     */
    private CodeInfo scan(Object endStep, String appId, String companyId, String batch) throws Exception {
        if (endStep.getClass().isAnnotationPresent(Trace.class)) {
            Trace trace = endStep.getClass().getAnnotation(Trace.class);
            if (trace.ended()) {
                String stepName = trace.stepName();
                List<EntityTraceBean> traces = branchMap.get("trace");
                if (traces.size() <= 0) {
                    LoggerUtil.getLogger().error("traceService->scan() 未匹配到有关流程");
                    return null;
                }
                if (!traces.get(0).getStepName().equals(stepName)) {
                    LoggerUtil.getLogger().error("traceService->scan() 寻找的流程最后一步和入参不匹配");
                    return null;
                }
                List<TraceInfo> infos = new ArrayList<>();
                System.out.println("============================================");
                parseTraceInfo(traces, endStep, infos, true);
                System.out.println("============================================");
                CodeInfo codeInfo = new CodeInfo();
                codeInfo.setContent(JSONObject.toJSONString(infos));
                codeInfo.setAppId(appId);
                codeInfo.setCompanyId(companyId);
                codeInfo.setCode(batch);
                codeInfo.setCreatetime(new Date());
                return codeInfo;
            } else {
                LoggerUtil.getLogger().error("traceService->scan() donot hava ended");
                return null;
            }
        } else {
            LoggerUtil.getLogger().error("traceService->scan() donot hava annotation Trace.class");
            return null;
        }
    }


    /**
     * 解析分支
     *
     * @param traces
     * @param map
     */
    private void parseBranch(List<EntityTraceBean> traces, Map<String, List<EntityTraceBean>> map) {
        if (traces != null && traces.size() > 0) {
            for (EntityTraceBean entityTraceBean : traces) {
                List<EntityTraceBean> branchBeans = map.get(entityTraceBean.getBranch());
                if (branchBeans == null) {
                    branchBeans = new ArrayList<>();
                    map.put(entityTraceBean.getBranch(), branchBeans);
                }
                branchBeans.add(entityTraceBean);
            }
        }
    }

    /**
     * 解析追溯流程
     *
     * @param traces
     */
    private void parseTraceInfo(List<EntityTraceBean> traces, Object start, List<TraceInfo> infos, boolean addFirst) throws Exception {
        LoggerUtil.logInfo("解析开始：" + start.getClass().getSimpleName());
        EntityTraceBean first = traces.get(0);
        Object temp = start;
        String lastStepId = getLastStep(start);
        if (addFirst) {
            TraceInfo firstTraceInfo = new TraceInfo(first, start);
            infos.add(firstTraceInfo);
            praseFlow(first, firstTraceInfo, start, infos);
        }
        //可以用作是否切换分支的
        for (int i = (addFirst ? 1 : 0); i < traces.size(); i++) {
            EntityTraceBean traceBean = traces.get(i);
            LoggerUtil.logInfo("解析流程：" + traceBean.getClassName() + "  lastStepId:" + lastStepId);
            Object info = null;
            if (traceBean.getRelationClass() != null && !traceBean.getRelationClass().equals("")) {
                info = sqlRelation(traceBean, lastStepId);
            } else {
                info = sqlObj(traceBean, temp);
            }
            TraceInfo traceInfo = new TraceInfo(traceBean, info);
            infos.add(traceInfo);
            boolean isOldBranch = praseFlow(traceBean, traceInfo, info, infos);
            if (!isOldBranch) {
                break;
            }
            temp = info;
            lastStepId = getLastStep(info);
        }
    }

    /**
     * 处理流程
     *
     * @param bean
     * @param info
     * @return ture原来分支，false分支
     */
    private boolean praseFlow(EntityTraceBean bean, TraceInfo info, Object object, List<TraceInfo> infos) throws Exception {
        if (bean.getChildren() != null && bean.getChildren().size() > 0) {
            info.setChildren(new ArrayList<>());
            parseChild(object, bean.getChildren(), info.getChildren());
        }
        //如果处理分支点
        if (bean.getCorssPront() != null && bean.getCorssPront().length > 0) {
            String crossPoint = getValueObj(object, "crossPoint");
            if (StringUtils.isEmpty(crossPoint)) {
                throw new BusinessException("500", "流程分支交叉点的crossPoint为空");
            }
            if (crossPoint.equals(bean.getBranch())) {
                return true;
            }
            //分支起点
            String crossStart = "";
            for (String item : bean.getCorssPront()) {
                if (item.startsWith(crossPoint)) {
                    crossStart = item.split(":")[1];
                    break;
                }
            }
            if (crossStart.equals("")) {
                throw new BusinessException("500", "未找到分支起点");
            }
            List<EntityTraceBean> branch = branchMap.get(crossPoint);
            if (branch != null && branch.size() > 0) {
                int flowStartPosition = -1;
                for (int i = 0; i < branch.size(); i++) {
                    EntityTraceBean item = branch.get(i);
                    if (item.getStepName().equals(crossStart)) {
                        flowStartPosition = i;
                        break;
                    }
                }
                if (flowStartPosition == -1) {
                    throw new BusinessException("500", "未找到分支流程位置");
                }
                List<EntityTraceBean> newbranch = branch.subList(flowStartPosition, branch.size());
                parseTraceInfo(newbranch, object, infos, false);
                return false;
            } else {
                LoggerUtil.logInfo("分支【" + crossPoint + "】没有流程点");
                throw new BusinessException("500", "分支【" + crossPoint + "】没有流程点");
            }
        }
        return true;
    }

    /**
     * 保存扫码信息
     *
     * @param code
     * @param endStep
     */
    public CodeInfo addInfo(String code, String batchNo, Object endStep, String appId, String companyId) {
        try {
            int p = codeInfoDao.countByBatchNoAndCode(batchNo, code);
            if (p > 0) {
                LoggerUtil.logError("traceService->addInfo() 信息已存在:" + code);
                return null;
            }
            CodeInfo codeInfo = scan(endStep, appId, companyId, code);
            codeInfo.setBatchNo(batchNo);
            codeInfoDao.save(codeInfo);
            LoggerUtil.logInfo("保存打码信成功");
            return codeInfo;
        } catch (Exception e) {
            e.printStackTrace();
            LoggerUtil.logError("traceService->addInfo() 保存扫码信息出错", e);
            throw new BusinessException("500", "保存扫码信息出错");
        }
    }


    /**
     * 保存扫码信息
     *
     * @param code
     * @param endStep
     */
    public CodeInfo addInfo(String code, String batchNo, Object endStep, String appId, String companyId, String branch) {
        try {
            int p = codeInfoDao.countByBatchNoAndCode(batchNo, code);
            if (p > 0) {
                LoggerUtil.logError("traceService->addInfo() 信息已存在:" + code);
                return null;
            }
            CodeInfo codeInfo = scan(endStep, appId, companyId, code);
            codeInfo.setBatchNo(batchNo);
            codeInfo.setBranch(branch);
            codeInfoDao.save(codeInfo);
            LoggerUtil.logInfo("保存打码信成功");
            return codeInfo;
        } catch (Exception e) {
            e.printStackTrace();
            LoggerUtil.logError("traceService->addInfo() 保存扫码信息出错", e);
            throw new BusinessException("500", "保存扫码信息出错");
        }

    }

    /**
     * 直接添加信息，针对直接采购分包
     *
     * @param code
     * @param info
     * @param appId
     * @param companyId
     * @return
     */
    public CodeInfo addObject(String code, String batchNo, Object info, String appId, String companyId) {
        int p1 = codeInfoDao.countByBatchNoAndCode(batchNo, code);
        if (p1 > 0) {
            LoggerUtil.logError("traceService->addInfo() 信息已存在:" + code);
            return null;
        }
        CodeInfo p = new CodeInfo();
        p.setAppId(appId);
        p.setCompanyId(companyId);
        p.setCode(code);
        p.setBatchNo(batchNo);
        p.setCreatetime(new Date());
        TraceInfo traceInfo = new TraceInfo();
        traceInfo.setStep("采购");
        traceInfo.setInfo(info);
        p.setContent(JSONObject.toJSONString(traceInfo));
        return codeInfoDao.save(p);
    }

    /**
     * 流程根目录
     *
     * @param traces
     * @return
     */
    private List<EntityTraceBean> getRoot(List<EntityTraceBean> traces) {
        Iterator<EntityTraceBean> it = traces.iterator();
        List<EntityTraceBean> root = new ArrayList<>();
        while (it.hasNext()) {
            EntityTraceBean item = it.next();
            if (item.getParentStepName() == null || item.getParentStepName().length == 0) {
                root.add(item);
                it.remove();
            }
        }
        return root;
    }

    /**
     * 递归流程
     *
     * @param traces
     * @param root
     */
    private void tree(List<EntityTraceBean> traces, List<EntityTraceBean> root) {
        if (root != null && root.size() > 0 && traces.size() > 0) {
            for (EntityTraceBean bean : root) {
                for (EntityTraceBean item : traces) {
                    if (contain(item.getParentStepName(), bean.getStepName())) {
                        List<EntityTraceBean> child = bean.getChildren();
                        if (child == null) {
                            child = new ArrayList<>();
                            bean.setChildren(child);
                        }
                        item.setParentStep(bean.getStepName());
                        child.add(item);
                    }
                }
                tree(traces, bean.getChildren());
            }
        }
    }

    private boolean contain(String[] strs, String str) {
        if (strs != null && strs.length > 0) {
            for (String item : strs) {
                if (item.equals(str)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 解析流程
     *
     * @param traces
     */
    private void parseRoot(List<EntityTraceBean> traces, Object start, List<TraceInfo> infos) throws Exception {
        EntityTraceBean first = traces.get(0);
        String lastStepId = getLastStep(start);
        TraceInfo firstTraceInfo = new TraceInfo(first, start);
        infos.add(firstTraceInfo);
        if (first.getChildren() != null && first.getChildren().size() > 0) {
            firstTraceInfo.setChildren(new ArrayList<>());
            parseChild(getObjId(start), first.getChildren(), firstTraceInfo.getChildren());
        }
        for (int i = 1; i < traces.size(); i++) {
            if (StringUtils.isEmpty(lastStepId)) {
                break;
            }
            EntityTraceBean traceBean = traces.get(i);
            Object info = null;
            if (traceBean.getRelationClass() != null && !traceBean.getRelationClass().equals("")) {
                info = sqlRelation(traceBean, lastStepId);
            } else {
                info = sqlObj(traceBean, lastStepId);
            }
            TraceInfo traceInfo = new TraceInfo(traceBean, info);
            infos.add(traceInfo);
            if (traceBean.getChildren() != null && traceBean.getChildren().size() > 0) {
                traceInfo.setChildren(new ArrayList<>());
                parseChild(getObjId(info), traceBean.getChildren(), traceInfo.getChildren());
            }
            lastStepId = getLastStep(info);
        }
    }

    /**
     * 从父节点往下走
     *
     * @param beforeId 父节点的id
     * @param traces
     * @param infos
     * @throws Exception
     */
    private void parseChild(String beforeId, List<EntityTraceBean> traces, List<TraceInfo> infos) throws Exception {
        if (traces != null && traces.size() > 0) {
            for (EntityTraceBean traceBean : traces) {
                Object object = sqlChild(traceBean, beforeId);
                TraceInfo info = new TraceInfo(traceBean, object);
                infos.add(info);
                if (traceBean.getChildren() != null && traceBean.getChildren().size() > 0) {
                    String id = getObjId(object);
                    List<TraceInfo> child = new ArrayList<>();
                    info.setChildren(child);
                    parseChild(id, traceBean.getChildren(), info.getChildren());
                }
            }
        }
    }

    /**
     * 从父节点往下走
     *
     * @param p      父节点的id
     * @param traces
     * @param infos
     * @throws Exception
     */
    private void parseChild(Object p, List<EntityTraceBean> traces, List<TraceInfo> infos) throws Exception {
        if (traces != null && traces.size() > 0) {
            for (EntityTraceBean traceBean : traces) {
                Object object = sqlChild(traceBean, p);
                TraceInfo info = new TraceInfo(traceBean, object);
                infos.add(info);
                if (traceBean.getChildren() != null && traceBean.getChildren().size() > 0) {
                    List<TraceInfo> child = new ArrayList<>();
                    info.setChildren(child);
                    if (object != null) {
                        List<Object> arrs = (List<Object>) object;
                        if (arrs.size() > 0) {
                            for (Object o : arrs) {
                                parseChild(object, traceBean.getChildren(), info.getChildren());
                            }
                        }
                    }
                }
            }
        }
    }


    private Object sqlObj(EntityTraceBean bean, Object object) throws Exception {
        String dao = bean.getClassName().replace("entity", "dao") + "Dao";
        BaseJpaDao baseJpaDao = (BaseJpaDao) ApplicationContextConfig.getBean(Class.forName(dao));
        StringBuilder sql = new StringBuilder();
        Object info = null;
        if (StringUtils.isEmpty(bean.getSql())) {
            String lastStepId = getLastStep(object);
            sql.append("select * from ").append(bean.getTableName()).append(" where ").append(" id='").append(lastStepId).append("'");
            if (!StringUtils.isEmpty(bean.getConditions())) {
                sql.append(" and ").append(bean.getConditions());
            }
            info = baseJpaDao.sqlQueryOne(sql.toString(), Class.forName(bean.getClassName()));
        } else {
            sql.append(bean.getSql());
            info = baseJpaDao.sqlQueryOne(sql.toString(), Class.forName(bean.getClassName()), getValues(object, bean.getSqlParams()));
        }
        LoggerUtil.logInfo("sql:" + sql.toString());
        return info;
    }

    private Object sqlObj(EntityTraceBean bean, String lastStepId) throws Exception {
        String dao = bean.getClassName().replace("entity", "dao") + "Dao";
        BaseJpaDao baseJpaDao = (BaseJpaDao) ApplicationContextConfig.getBean(Class.forName(dao));
        StringBuilder sql = new StringBuilder();
        sql.append("select * from ").append(bean.getTableName()).append(" where ").append(" id='").append(lastStepId).append("'");
        if (!StringUtils.isEmpty(bean.getConditions())) {
            sql.append(" and ").append(bean.getConditions());
        }
        Object info = baseJpaDao.sqlQueryOne(sql.toString(), Class.forName(bean.getClassName()), null);
        LoggerUtil.logInfo("sql:" + sql.toString());
        return info;
    }

    private Object sqlChild(EntityTraceBean bean, String beforeId) throws Exception {
        String dao = bean.getClassName().replace("entity", "dao") + "Dao";
        StringBuilder sql = new StringBuilder();
        sql.append("select * from ").append(bean.getTableName()).append(" where ").append(" last_step='").append(beforeId).append("'");
        if (!StringUtils.isEmpty(bean.getConditions())) {
            sql.append(" and ").append(bean.getConditions());
        }
        BaseJpaDao baseJpaDao = (BaseJpaDao) ApplicationContextConfig.getBean(Class.forName(dao));
        Object info = baseJpaDao.sqlQueryList(sql.toString(), Class.forName(bean.getClassName()));
        ;
        LoggerUtil.logInfo("sql:" + sql.toString());
        return info;
    }

    private Object sqlChild(EntityTraceBean bean, Object object) throws Exception {
        String dao = bean.getClassName().replace("entity", "dao") + "Dao";
        BaseJpaDao baseJpaDao = (BaseJpaDao) ApplicationContextConfig.getBean(Class.forName(dao));
        Object info = null;
        StringBuilder sql = new StringBuilder();
        if (StringUtils.isEmpty(bean.getSql())) {
            String beforeId = getObjId(object);
            sql.append("select * from ").append(bean.getTableName()).append(" where ").append(" last_step='").append(beforeId).append("'");
            if (!StringUtils.isEmpty(bean.getConditions())) {
                sql.append(" and ").append(bean.getConditions());
            }
            info = baseJpaDao.sqlQueryList(sql.toString(), Class.forName(bean.getClassName()));
            ;
        } else {
            sql.append(bean.getSql());
            info = baseJpaDao.sqlQueryList(sql.toString(), Class.forName(bean.getClassName()), getValues(object, bean.getSqlParams()));
            ;
        }
        LoggerUtil.logInfo("sql:" + sql.toString());
        return info;
    }

    private Object sqlRelation(EntityTraceBean bean, String beforeId) throws Exception {
        String dao = bean.getClassName().replace("entity", "dao") + "Dao";
        StringBuilder sql = new StringBuilder();
        sql.append("select * from ").append(bean.getTableName()).append(" where id in ( select ").append(bean.getMainColumn());
        sql.append(" from ").append(bean.getRelationClass()).append(" where ").append(bean.getRelationColumn()).append("='");
        sql.append(beforeId).append("'").append(")");
        BaseJpaDao baseJpaDao = (BaseJpaDao) ApplicationContextConfig.getBean(Class.forName(dao));
        Object info = baseJpaDao.sqlQueryList(sql.toString(), Class.forName(bean.getClassName()));
        ;
        return info;
    }

    private String[] getValues(Object obj, String[] params) throws Exception {
        String[] values = new String[params.length];
        if (params != null && params.length > 0) {
            for (int i = 0; i < values.length; i++) {
                values[i] = getValueObj(obj, params[i]);
            }
        }
        return values;
    }

    private String getLastStep(Object obj) throws Exception {
        try {
            Field field = obj.getClass().getSuperclass().getDeclaredField("lastStep");
            field.setAccessible(true);
            Object o = field.get(obj);
            if (o != null) {
                return field.get(obj).toString();
            } else {
                return null;
            }
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 获取属性
     *
     * @param obj
     * @return
     * @throws Exception
     */
    private String getObjId(Object obj) throws Exception {
        Field field = obj.getClass().getSuperclass().getDeclaredField("id");
        field.setAccessible(true);
        Object o = field.get(obj);
        if (o != null) {
            return field.get(obj).toString();
        } else {
            return null;
        }
    }

    /**
     * 获取属性值
     *
     * @param obj
     * @param fieldName
     * @return
     * @throws Exception
     */
    private String getValueObj(Object obj, String fieldName) throws Exception {
        Field field = null;
        try {
            field = obj.getClass().getSuperclass().getDeclaredField(fieldName);
        } catch (Exception e) {
            try {
                field = obj.getClass().getDeclaredField(fieldName);
            } catch (Exception e1) {

            }
        }
        if (field != null) {
            field.setAccessible(true);
            Object o = field.get(obj);
            if (o != null) {
                return field.get(obj).toString();
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    public CodeInfo addCodeInfo(CodeInfo info) {
        return codeInfoDao.save(info);
    }

    public List<EntityTraceBean> getTraces() {
        return traces;
    }

    public void setTraces(List<EntityTraceBean> traces) {
        this.traces = traces;
    }

    public Map<String, List<EntityTraceBean>> getBranchMap() {
        return branchMap;
    }

    public void setBranchMap(Map<String, List<EntityTraceBean>> branchMap) {
        this.branchMap = branchMap;
    }

}
